<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>第四章：Chains模块 - 4.3 Sequential Chains：按顺序链接多个Chain</title>
    <link rel="stylesheet" href="../shared_styles.css">
</head>
<body>
    <div class="container">
        <h2>4.3 Sequential Chains：按顺序链接多个Chain</h2>
        <p>Sequential Chains（顺序链）允许我们将多个组件按特定顺序连接起来，其中一个组件的输出可以作为下一个组件的输入。在现代Langchain中，这些功能通常通过Langchain表达式语言（LCEL）直接组合Runnable来实现。下面我们将展示如何用LCEL实现类似的功能，取代旧的 <code>SimpleSequentialChain</code> 和 <code>SequentialChain</code>。</p>
            
            <h3 id="4-3-1">4.3.1 LCEL 实现简单顺序调用</h3>
            <p>旧的 <code>SimpleSequentialChain</code> 适用于线性的、单输入单输出的链式调用。使用LCEL，我们可以通过管道操作符 <code>|</code> 和可能的 <code>RunnableLambda</code> 来实现这一点。</p>
            <pre><code class="language-python">
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnableLambda
from langchain_core.messages import AIMessage # 用于类型提示
from dotenv import load_dotenv
import os

# (Qwen LLM 初始化代码同上)
load_dotenv()
api_key = os.environ.get("DASHSCOPE_API_KEY")
qwen_llm = ChatOpenAI(base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key=api_key, model="qwen-plus", temperature=0.7)

# 第一个Runnable: 生成一个关于某个主题的标题
prompt_title = PromptTemplate.from_template("为关于 {topic} 的戏剧写一个引人入胜的标题。")
chain_title_runnable = prompt_title | qwen_llm

# 第二个Runnable: 根据标题写一个简短的剧情简介
prompt_synopsis = PromptTemplate.from_template(
    "戏剧标题: {title}\n请为这个戏剧写一个两句话的剧情简介。"
)
chain_synopsis_runnable = prompt_synopsis | qwen_llm

# 定义一个函数来准备第二个runnable的输入
def prepare_synopsis_input(ai_message_title: AIMessage) -> dict:
    # ai_message_title 是 chain_title_runnable 的输出 (AIMessage)
    # chain_synopsis_runnable 的 prompt (prompt_synopsis) 需要一个名为 "title" 的输入
    return {"title": ai_message_title.content}

# 使用LCEL构建顺序链
# 1. chain_title_runnable 处理初始输入 {"topic": ...}
# 2. RunnableLambda(prepare_synopsis_input) 将上一步的输出转换为 chain_synopsis_runnable 的输入格式
# 3. chain_synopsis_runnable 生成最终剧情简介
overall_simple_chain_lcel = chain_title_runnable | RunnableLambda(prepare_synopsis_input) | chain_synopsis_runnable

# 调用整个顺序链
# 输入是第一个runnable的输入变量
result_simple_lcel = overall_simple_chain_lcel.invoke({"topic": "人工智能的未来"})
print(f"\nLCEL顺序链的最终输出 (AIMessage):\n{result_simple_lcel}")
print(f"\n最终剧情简介:\n{result_simple_lcel.content}")
            </code></pre>
            <p class="note">使用LCEL时，我们需要手动管理数据流。<code>RunnableLambda</code> 在这里用于将前一个LLM调用的输出（一个<code>AIMessage</code>对象）转换为下一个LLM调用所期望的输入字典。</p>

            <h3 id="4-3-2">4.3.2 LCEL 实现更通用的顺序调用 (管理多个输入/输出)</h3>
            <p>旧的 <code>SequentialChain</code> 提供了更大的灵活性。使用LCEL，我们可以通过 <code>RunnablePassthrough.assign</code> 和 <code>RunnableLambda</code> 来管理和传递多个命名变量。</p>
            <pre><code class="language-python">
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
from langchain_core.runnables import RunnablePassthrough, RunnableLambda
from langchain_core.messages import AIMessage # 用于类型提示
from dotenv import load_dotenv
import os

# (Qwen LLM 初始化代码同上)
load_dotenv()
api_key = os.environ.get("DASHSCOPE_API_KEY")
qwen_llm = ChatOpenAI(base_url="https://dashscope.aliyuncs.com/compatible-mode/v1", api_key=api_key, model="qwen-plus", temperature=0.7)

# Runnable 1: 生成产品名称
prompt_product_name = PromptTemplate.from_template(
    "为一个基于 {technology} 的创新产品想一个酷炫的名字。"
)
chain_product_name_runnable = prompt_product_name | qwen_llm

# Runnable 2: 为产品写一句宣传语
prompt_tagline = PromptTemplate.from_template(
    "产品名称: {product_name}\n为这个产品写一句吸引人的宣传语 (不超过15个字)。"
)
chain_tagline_runnable = prompt_tagline | qwen_llm

# Runnable 3: 结合产品名称和宣传语，生成一段简短描述
prompt_description = PromptTemplate.from_template(
    "产品名称: {product_name}\n宣传语: {tagline}\n请为这个产品写一段50字左右的简短描述，突出其主要特点。"
)
chain_description_runnable = prompt_description | qwen_llm

# 使用LCEL构建SequentialChain的等效功能
# RunnablePassthrough.assign 用于在传递的字典中添加新的键值对
overall_sequential_chain_lcel = (
    RunnablePassthrough.assign( # 初始输入: {"technology": ...}
        # 执行 chain_product_name_runnable，将其 AIMessage 输出赋给 product_name_raw
        product_name_raw=chain_product_name_runnable 
    )
    | RunnablePassthrough.assign( # 输入: {"technology": ..., "product_name_raw": AIMessage}
        # 从 AIMessage 中提取内容，赋给 product_name
        product_name=RunnableLambda(lambda x: x["product_name_raw"].content)
    )
    | RunnablePassthrough.assign( # 输入: {"technology": ..., "product_name_raw": ..., "product_name": str}
        # 为 chain_tagline_runnable 准备输入，并执行，将 AIMessage 输出赋给 tagline_raw
        tagline_raw=RunnableLambda(lambda x: {"product_name": x["product_name"]}) | chain_tagline_runnable
    )
    | RunnablePassthrough.assign( # 输入: ... , "tagline_raw": AIMessage
        # 从 AIMessage 中提取内容，赋给 tagline
        tagline=RunnableLambda(lambda x: x["tagline_raw"].content)
    )
    | RunnablePassthrough.assign( # 输入: ... , "tagline": str
        # 为 chain_description_runnable 准备输入，并执行，将 AIMessage 输出赋给 description_raw
        description_raw=RunnableLambda(
            lambda x: {"product_name": x["product_name"], "tagline": x["tagline"]}
        ) | chain_description_runnable
    )
    | RunnablePassthrough.assign( # 输入: ... , "description_raw": AIMessage
        # 从 AIMessage 中提取内容，赋给 description
        description=RunnableLambda(lambda x: x["description_raw"].content)
    )
    # 最后，选择我们希望输出的字段
    | RunnableLambda(lambda x: {
        "technology": x["technology"],
        "product_name": x["product_name"],
        "tagline": x["tagline"],
        "description": x["description"]
      })
)

# 调用整个顺序链
input_data = {"technology": "量子计算"}
result_sequential_lcel = overall_sequential_chain_lcel.invoke(input_data)

print(f"\nLCEL SequentialChain 输入: {input_data}")
print(f"LCEL SequentialChain 输出: {result_sequential_lcel}")
print(f"\n产品名称: {result_sequential_lcel['product_name']}")
print(f"宣传语: {result_sequential_lcel['tagline']}")
print(f"产品描述: {result_sequential_lcel['description']}")
            </code></pre>
            <p>在LCEL中，通过 <code>RunnablePassthrough.assign</code> 和 <code>RunnableLambda</code> 组合，可以灵活地处理和传递数据流，实现复杂的顺序逻辑，同时保持代码的可读性。</p>
        <div class="navigation">
            <a href="../langchain_tutorial_outline.html">返回目录</a>
            <a href="../chapter4/4-2_llmchain_basic_chain.html">上一小节：LLMChain：最基础的Chain</a>
            <a href="../chapter4/4-4_router_chains.html">下一小节：Router Chains：根据输入动态选择下一个Chain (进阶)</a>
        </div>
    </div>
</body>
</html>
